Capstone

Autor

IVT; JMR

Data de Publicação

16 de novembro de 2025

1 1. Introdução

O presente projeto foi desenvolvido no âmbito do Capstone do curso Fundamentos de Data Science na Saúde: Aplicações com R da Escola Nacional de Saúde Pública (Escola Nacional de Saúde Pública (ENSP NOVA) 2025).

Fonte: (RStudio, Inc. 2025)

O trabalho tem como propósito aplicar técnicas de ciência de dados na análise de uma base real de morbilidade hospitalar, a base de Grupos de Diagnóstico Homogéneo (GDH) disponibilizada pela Administração Central do Sistema de Saúde (ACSS). A crescente complexidade dos cuidados hospitalares e a transição para o modelo das Unidades Locais de Saúde (ULS) colocam desafios importantes para a gestão integrada dos recursos e para a equidade no acesso. Neste contexto, comparar realidades distintas (como as ULS do Médio Ave e de Amadora-Sintra) permite compreender diferenças territoriais na carga de doença e na resposta dos serviços hospitalares, contribuindo para uma visão mais informada sobre o desempenho do SNS. Assim, este estudo combina análise demográfica, epidemiológica e operacional, explorando a base de dados de internamentos hospitalares (GDH) e relacionando-a com variáveis geográficas e populacionais. O objetivo final é demonstrar o potencial da análise de dados em R como ferramenta de apoio à tomada de decisão em saúde pública e à avaliação do desempenho das ULS.

1.0.1 A ULS

  • imagem

  • Caracterizar a população residente nas áreas correspondentes às duas ULS (população 2017)

    • Distribuição idade e sexo

  • Concelhos

    • Amadora

    • Sintra

  • Concelhos

    • Santo Tirso

    • Trofa

    • Vila Nova de Famalicão

  • total de residentes (2017)

1.0.2 Bases de dados utilizadas

  • base_gdh_2017_icd10_no_freg

  • codigos_diagnostico_icd10

  • geo_linkage_2024_v6

  • grupos_gdh

  • INE - Populacao 2017 -

1.1 2. Objetivos do estudo

1.2 2.1. Objetivo geral

O projecto centra-se na análise comparativa da situação nas ULS do Médio Ave e Amadora-Sintra.

Analisar e comparar os padrões de morbilidade hospitalar e a resposta assistencial entre as Unidades Locais de Saúde (ULS) do Médio Ave e Amadora-Sintra, utilizando dados da base de Grupos de Diagnóstico Homogéneo (GDH) do SNS, com foco na caracterização demográfica, perfil de diagnósticos e desempenho hospitalar. Objetivos específicos

  1. ⁠ ⁠Caracterizar a população residente nas áreas correspondentes às duas ULS, segundo idade, sexo e perfil de morbilidade.
  2. Identificar e comparar os principais grupos de diagnósticos (GDH) e condições de saúde mais prevalentes em cada região.
  3. ⁠Avaliar diferenças entre ULS
    1. limitações de codificação e de qualidade dos dados.

1.3 2.2. Objetivos específicos

  • Caracterizar a população residente nos concelhos correspondentes às áreas das ULS Amadora/Sintra

    • distribuição idade e sexo

    • concelho

  • Caracterizar os problemas de saúde e identificar os mais frequentes (base)

    • comparar totais
    • duração de internamento
    • Perfil de morbilidade (mortalidade)
    • problemas de saúde (top 5)
    • Taxa de mortalidade

2 3. Importação e preparação dos dados

Código
set.seed(123)

rm(list = ls(all.names = TRUE)) 
required_packages <- c(
                       "tidyverse",
                       "rio",
                       "scales",
                       "here",
                       "patchwork",
                       "sf", 
                       "ggthemes",
                       "giscoR",
                       "eurostat",
                       "sysfonts", 
                       "showtext", 
                       "scales",
                       "geodata", 
                       "osmdata", 
                       "leaflet",
                       "janitor",
                       "assertr",
                       "data.validator",
                       "forcats",
                       "data.table",
                       "broom",
                       "gt",
                       "gtsummary",
                       "glm2",
                       "performance",
                       "see",
                       "readxl",
                       "purrr",
                       "tibble",
                       "ggridges",
                       "tidymodels",
                       "tidyclust",
                       "factoextra"
                       )    

for (pkg in required_packages) {
 
  if (!pkg %in% rownames(installed.packages())) {
    install.packages(pkg)
  }

  library(pkg, character.only = TRUE)
}
remove(required_packages)
remove(pkg)

2.1 3.1 Importação dos dados

Código
gdh_base_raw <-  import("data/data_gdh/base_gdh_icd10_no_freg.csv")

code_residence <- import("data/data_gdh/codigos_residencia.csv") 


geo_linkage <- import("data/data_gdh/geo_linkage_2024_v6.csv")

comm_pt <- st_read("data/map_json_portugal/concelhos_portugal_light.json")
Reading layer `concelhos_portugal' from data source 
  `/Users/joaomiguelrocha/documents_local/GitHub/capstone_r_fundamentals/data/map_json_portugal/concelhos_portugal_light.json' 
  using driver `TopoJSON'
Simple feature collection with 278 features and 5 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -9.50088 ymin: 37.01021 xmax: -6.189142 ymax: 42.1482
CRS:           NA
Código
icd10_codes <- import("data/data_gdh/codigos_diagnostico_icd10.csv")

gdh_group <- import("data/data_gdh/grupos_gdh.csv")

# residentes_2017 <- read_delim("data/populacao_residente_2013.csv", 
#     delim = ";", 
#     escape_double = FALSE, 
#     trim_ws = TRUE)

3 4. Análise Exploratória e Limpeza

Código
df_summarise <- function(df) {
    tibble(
      variable = names(df),
      type = map_chr(df, ~ class(.x)[1]),
      n_total = nrow(df),
      n_distinct = map_int(df, ~ if (is.character(.x) || is.factor(.x))
                              dplyr::n_distinct(.x, na.rm = TRUE) # especifica package para evitar conflito
                            else NA_integer_),
    n_NA = map_int(df, ~ {
      if (is.character(.x)) {
        sum(is.na(.x) | .x == "", na.rm = TRUE)  # contar NA e células em branco 
      } else {
        sum(is.na(.x))
      }
    })
  )
}
Código
# Funções 
## Mudar para português

var_summarise <- summarise_variable <- function(x) {
  if (is.numeric(x)) {
    out <- list(
      Min = min(x, na.rm = TRUE),
      Max = max(x, na.rm = TRUE),
      Mean = mean(x, na.rm = TRUE),
      Median = median(x, na.rm = TRUE),
      Quantiles = quantile(x, na.rm = TRUE),
      Total = sum(x, na.rm = TRUE),
      NAs = sum(is.na(x))
    )
  } else if (is.character(x) || is.factor(x)) {
    tbl <- table(x, useNA = "ifany")
    freq <- as.numeric(tbl)
    prop <- as.numeric(prop.table(tbl))
    cum_freq <- cumsum(freq)
    names(freq) <- names(prop) <- names(tbl)
    out <- list(
      Unique_Values = unique(na.omit(x)),
      Frequency = freq,
      Proportion = prop,
      CumFreq = cum_freq,
      NAs = sum(is.na(x))
    )
  } else {
    out <- list(Warning = "Variable type not supported")
  }
  return(out)
}

var_summarise_df <- function(df, x) {
  # Extract the variable vector from df using x as column name (string)
  var <- df[[x]]
  
  if (is.numeric(var)) {
    df_out <- data.frame(
      Statistic = c("Min", "Max", "Mean", "Median", "Total", "NAs"),
      Value = c(
        min(var, na.rm = TRUE),
        max(var, na.rm = TRUE),
        mean(var, na.rm = TRUE),
        median(var, na.rm = TRUE),
        sum(var, na.rm = TRUE),
        sum(is.na(var))
      )
    )
    # Add quantiles as separate rows
    quants <- quantile(var, na.rm = TRUE)
    quant_df <- data.frame(
      Statistic = paste0("Quantile_", names(quants)),
      Value = as.numeric(quants)
    )
    df_out <- rbind(df_out, quant_df)
  } else if (is.character(var) || is.factor(var)) {
    tbl <- table(var, useNA = "ifany")
    freq <- as.numeric(tbl)
    prop <- prop.table(tbl)
    cum_freq <- cumsum(freq)
    df_out <- data.frame(
      Value = names(tbl),
      Frequency = freq,
      Proportion = prop,
      CumFrequency = cum_freq
    )
  } else {
    df_out <- data.frame(Warning = "Variable type not supported")
  }
  
  return(df_out)
}

4

4.1 Base Gdh e variáveis

Código
# Base dados gdh 
# checking for NA
gdh_raw_summary <- df_summarise(gdh_base_raw)

tabela0 <- gdh_raw_summary |> 
  gt()

tabela0
variable type n_total n_distinct n_NA
seq_number character 1711879 1711869 0
hosp_id character 1711879 56 0
sexo character 1711879 3 0
idade integer 1711879 NA 0
distrito integer 1711879 NA 0
concelho integer 1711879 NA 0
hora_entrada integer 1711879 NA 0
dias_int integer 1711879 NA 0
dsp character 1711879 9 13
adm_tip character 1711879 2 0
hora_urgência integer 1711879 NA 1327998
gcd_APR31 integer 1711879 NA 0
tipo_port APR31 character 1711879 2 0
severidade_APR31 character 1711879 5 225
mortalidade_APR31 character 1711879 5 225
freguesia integer 1711879 NA 0
cod_diagnostico character 1711879 18955 0
Código
gtsave(tabela0, filename = "gdh_tabela_limpeza.html") 

Avaliação inicial

Total observações vs NA

  • n = 1711879

  • NAs

    • hora_urgencia: n(1.327.998) - vamos usar? se não apagar

    • severidade: n(225) em branco -> transformar em “desconhecido”

    • mortalidade n(225) em branco -> “NA” - transformar em desconhecido para proporções

Erros tipo variaveis

  • gcd_apr31 - int devia ser chr

  • concelho e distrito - estão como int - sao codigos numéricos deve ser chr

  • freguesia - remover, sem valores que possamos utilizar

Código
# mapeia valores presentes na base de dados de categorias de variávies chr 

gdh_unique <- gdh_base_raw |>
  select(where(is.character)) |> 
  map(unique)

Outros:

  • dias internamento - como interpretar? remover 0?

  • o que seria expectavel (dicionario) vs obs variaveis

    • adm_tip - apenas dois tipos, mas 0 missings

    • dsp - como interpretar? tratar falecidos

seq_number - sem NA 
hosp_id - 56 possiveis - algum missing? 
sexo - 3 variaveis como esperado, 
idade - todas têm entrada - 0 são mesmo idade? se tiver meses? 
distrito - transformar em chr e contar diferentes (30 valores possiveis)
concelho - igual a distrito 
freguesia - inútil - remover 
hora_entrada - valores em s, a partir das 0h do dia da entrada na instituição - verificar 0 e como interpretar  
dias_int - Total de dias de estadia do utente na instituição de saúde, em conformidade com a definição estatística de tempo de internamento, constante na portaria em vigor à data de extração dos dados.- qual a definicao estatistica? 
dsp - 9 valores possiveis, 9 entradas , contar "Desconhecido" como NA 
adm_tip - 8 valores possiveis, apenas 2 , 0 NAs
hora_urgência - Hora de entrada no serviço de urgência da instituição de saúde. Os valores são apresentados em segundos, contados a partir das zero horas do dia em que o utente deu entrada, no serviço de urgência da instituição - 
gcd_APR31 - mudar para chr, 27 valores possiveis, 99 - Erro
tipo_port APR31 - 2 possiveis, 0 NA - bem 
severidade_APR31 - mortalidade_APR31 - 4 valores possiveis existem 5 - 225 NA são todos missing
cod_diagnostico - ver se dao todos match com icd10.csv 

4.2 Limpeza

Código
gdh_base_clean <- gdh_base_raw |> 
  clean_names() |> 
  rename(
    cod_dist = distrito,
    cod_conc = concelho,
    icd10_code = cod_diagnostico
  ) |> 
  mutate(
    across(c(cod_dist, cod_conc), ~sprintf("%02d", .x), .names = "{.col}_chr"),
    dt_mun = paste0(cod_dist_chr, cod_conc_chr),
    code_length = nchar(icd10_code),
    idade_cat = cut(idade, c(0, 18, 65, Inf), right = FALSE)
    ) |> 
  filter( 
    idade != -1, 
    cod_conc_chr != "99",  
    cod_dist_chr != "99", 
    dt_mun != "9999", 
    sexo != "Indeterminado"
    ) |> 
  select(
    -hora_urgencia,
    -freguesia,
    -hosp_id,
    -hora_entrada)
Código
# Summary Dados Limpos 

gdh_clean_summary <- df_summarise(gdh_base_clean)

tabela0_1 <- gdh_clean_summary |> 
  gt()

tabela0_1
variable type n_total n_distinct n_NA
seq_number character 1560164 1560154 0
sexo character 1560164 2 0
idade integer 1560164 NA 0
cod_dist integer 1560164 NA 0
cod_conc integer 1560164 NA 0
dias_int integer 1560164 NA 0
dsp character 1560164 9 13
adm_tip character 1560164 2 0
gcd_apr31 integer 1560164 NA 0
tipo_port_apr31 character 1560164 2 0
severidade_apr31 character 1560164 5 208
mortalidade_apr31 character 1560164 5 208
icd10_code character 1560164 18354 0
cod_dist_chr character 1560164 29 0
cod_conc_chr character 1560164 24 0
dt_mun character 1560164 308 0
code_length integer 1560164 NA 0
idade_cat factor 1560164 3 0

Fazer o mesmo para restantes bases de dados

4.2.1 ICD10

Código
icd10_summary <- df_summarise(icd10_codes)

tabela01 <- icd10_summary |> 
  gt() 

tabela01 
variable type n_total n_distinct n_NA
Código ICD-10-CM character 95093 94569 0
Short_Descp ICD-10-CM character 95093 94233 0
Long_Descp ICD-10-CM character 95093 94319 0
PT character 95093 92964 1180
Código
icd10_codes_pt <- icd10_codes |> 
  clean_names() |>
  rename(
    descricao_pt = pt,
    descricao_en = short_descp_icd_10_cm, 
    icd10_code = codigo_icd_10_cm
  ) |> 
  select(
    icd10_code,
    descricao_en,
    descricao_pt
  ) |> 
  mutate(
    code_length = nchar(icd10_code)
  ) |> 
  distinct(icd10_code, .keep_all = TRUE) # remove duplicados 

# total_icd10 <- gdh_join_final |>
#   group_by(icd10_code) |>
#   summarise(total = n()) |>
#   arrange(desc(total)) |>
#   right_join(
#     icd10_codes_pt,
#     by = "icd10_code"
#   )
# 
# total_icd10 <- gdh_join_final |>
#   group_by(uls_2024, dt_mun, icd10_code) |>
#   summarise(total = n()) |>
#   arrange(desc(total)) |>
#   left_join(
#     icd10_codes_pt,
#     by = "icd10_code"
#   )
# 
# total_icd10_uls <- gdh_join_final |>
#   group_by(icd10_code, tipo_port_apr31, uls_2024) %>%
#   summarise(total = n()) %>%
#   arrange(desc(total))
  • todas as variáveis são chr

  • PT: 1180 empty -> mas descrição em inglês está preenchida

  • remover descrição longa em Inglês

  • apagados duplicados

4.2.2 GDH

Código
gdh_group_summary <- df_summarise(gdh_group)

tabela02 <- gdh_group_summary |> 
  gt() 

tabela02
variable type n_total n_distinct n_NA
gcd_APR31 integer 27 NA 0
Designacao character 27 27 0
Código
gdh_group <- gdh_group |> 
  clean_names() |> 
  rename(
    descricao_apr31 = designacao
  )
  • gcd_apr31 - int em vez de chr - transformar

4.2.3 Code Residence

Código
code_residence_sum <- df_summarise(code_residence)

tabela03 <- code_residence_sum |> 
  gt() 

tabela03
variable type n_total n_distinct n_NA
COD_DIST integer 313 NA 0
COD_CONC integer 313 NA 0
COD_FREG integer 313 NA 0
DES_DCF character 313 311 0
Código
code_residence <- code_residence |> # APAGAR NAO E NECESSARIO
  clean_names() |> 
  mutate(
    across(c(cod_dist, cod_conc), ~sprintf("%02d", .x), .names = "{.col}_chr"),
    dt_mun = paste0(cod_dist_chr, cod_conc_chr)
    )

# transformar int em chr

4.2.4 COMM-PT

Código
comm_pt_summary <- df_summarise(comm_pt)

tabela04 <- comm_pt_summary |> 
  gt() 

tabela04
variable type n_total n_distinct n_NA
id character 278 0 278
GID_1 character 278 18 0
NAME_1 character 278 18 0
GID_2 character 278 278 0
NAME_2 character 278 278 0
geometry sfc_MULTIPOLYGON 278 NA 0
Código
comm_pt <- comm_pt |>
  rename(municipio = NAME_2, distrito = NAME_1) |> 
  clean_names()

4.2.5 Geo linkage

Código
geo_linkage_summary <- df_summarise(geo_linkage)

tabela05 <- geo_linkage_summary |> 
  gt() 

tabela05
variable type n_total n_distinct n_NA
freguesia_2025 character 3260 2989 0
abreviatura_freguesia_2025 character 3260 2988 0
freguesia_2013 character 3260 2874 2
fr_2025 character 3260 111 0
dicofre_2025 character 3260 3259 0
dicofre_2013 character 3260 3093 2
dicofre_2013_2 character 3260 3093 2
municipio_2013 character 3260 308 0
dt_mun integer 3260 NA 0
mn_2025 integer 3260 NA 0
municipio_2024_cod character 3260 308 0
municipio_2013_cod character 3260 308 0
municipio_2002_cod character 3260 308 0
distrito_2013 character 3260 29 0
dt_2025 integer 3260 NA 0
distrito_2013_cod integer 3260 NA 0
nuts3_2013 character 3260 25 0
nuts3_2013_cod character 3260 25 0
nuts3_2002_cod integer 3260 NA 223
nuts3_2024 character 3260 26 0
nuts3_2024_cod character 3260 26 0
nuts2_2013 character 3260 7 0
nuts2_2013_cod integer 3260 NA 0
nuts2_2024 character 3260 9 0
nuts2_2024_cod character 3260 9 0
nuts1_2013 character 3260 3 0
nuts1_2013_cod integer 3260 NA 0
pais character 3260 1 0
pais_cod character 3260 1 0
uls_hierarquia_csp character 3260 40 210
uls_2024 character 3260 55 0
uls_2023 character 3260 55 0
aces_2022 character 3260 72 0
aces_2022_cod integer 3260 NA 0
ars_2022 character 3260 7 0
ars_2022_cod integer 3260 NA 0
regiao_2024 character 3260 7 0
Código
geolink_target <- c("dt_mun", "distrito_2013","municipio_2013_cod", "municipio_2013", "uls_2024", "regiao_2024", "nuts3_2013", "nuts3_2013_cod", "nuts3_2024", "nuts3_2024_cod", "municipio_2024_cod", "uls_2023", "uls_hierarquia_csp")


geo_linkage_clean <- geo_linkage |>
  clean_names() |>  
  mutate(
    dt_mun = str_pad(as.character(dt_mun), 4, pad = 0),
    mun_cod = str_pad(as.character(municipio_2013_cod), 2, pad = 0),
    dt_cod = str_pad(as.character(distrito_2013_cod), 2, pad = 0)
    ) |> 
  select(all_of(geolink_target)) |> 
  distinct(dt_mun, .keep_all = TRUE) |>
  rename(
    municipio = municipio_2013,
    distrito = distrito_2013
  )

4.3 Join bases

Código
# CÓDIGOS RESIDÊNCIA 
gdh_base_join <- gdh_base_clean |> 
  left_join(
    code_residence,
    by = "dt_mun"
  )

# join gdh group and icd10 codes
gdh_base_join <- gdh_base_clean |> 
  left_join(
    icd10_codes_pt, by = c("icd10_code", "code_length")
  ) |> 
  left_join(
    gdh_group, by = "gcd_apr31"
    )




# join concelho e municipio 
# foi necessario criar vetor - passar de int a chr 
# geo_linkage - filtrar repetidos (concelho) para join



# residence_geo_join <- geo_linkage_target |> 
  # left_join(
  #   code_residence,
  #   geo_linkage_target,
  #   by = "dt_mun") 

gdh_join_final <- gdh_base_join |> 
  left_join(
    geo_linkage_clean,
    by = "dt_mun") 

# Base de dados apenas com ULS Amadora/Sintra, ULS Médio Ave
gdh_uls <- gdh_join_final |>
  filter(
    cod_dist != 99
  ) |> 
  filter(uls_2024 %in% c("ULS de Amadora/Sintra", "ULS do Médio Ave")) 



# distribuicao_idade <- function(data, uls) |> 
#   ggplot() |> 
#   geom_jitter(aes(x = idade, y = dias_internamento, fill = sexo)) +
#   
# p1 <- distribuicao_idade(gdh_base_clean, "uls medio ave")

5 Análise Exploratória

5.0.1 Idade

Código
# variabilidade 

idade_sumario <- var_summarise_df(gdh_base_clean, "idade")

idade_sumario
       Statistic        Value
1            Min 0.000000e+00
2            Max 1.090000e+02
3           Mean 5.667767e+01
4         Median 6.300000e+01
5          Total 8.842646e+07
6            NAs 0.000000e+00
7    Quantile_0% 0.000000e+00
8   Quantile_25% 4.300000e+01
9   Quantile_50% 6.300000e+01
10  Quantile_75% 7.500000e+01
11 Quantile_100% 1.090000e+02
Código
# Base dados completa
p4_0 <- gdh_base_clean |>
  ggplot() +
  geom_boxplot(
    aes(y = idade)
    ) +
  # facet_grid(~sexo) +
  theme_minimal() +
  theme(
    axis.line.x = element_blank(),
    axis.text.x = element_blank()
  )
  
p4_0

Código
idade_sumario <- idade_sumario |> 
  mutate(
    value = round(Value, 0)
  ) |> 
  filter(
    Statistic != "Total"
  )

# add labels - ver como corrigir 
p4_01 <- gdh_base_clean |>
  ggplot() +
  geom_boxplot(
    aes(x = 1, y = idade)
    ) +
  geom_text(
    data = idade_sumario, 
    aes(x = 1, y = value, label = value),
    hjust = -0.3,
    vjust = -0.3
    ) + 
  theme_minimal() +
  theme(
    axis.line.x = element_blank(),
    axis.text.x = element_blank()
  )
  
p4_01

Código
# ULS MAve e ULS A/S


p4_02 <- gdh_base_clean |>
  ggplot() +
  geom_density(
    aes(x = idade)
    ) +
  facet_grid(~sexo) +
  theme_minimal() 
  # theme(
  #   axis.line.x = element_blank(),
  #   axis.text.x = element_blank()
  # )
  
p4_02

Código
# 

# p4_03 <- gdh_base_clean |>
#   filter(
#     sexo != "Indeterminado"
#   ) |>
#   ggplot() +
#   geom_density(
#     aes(x = idade)
#     ) +
#   geom_boxplot(
#     aes(x = idade, y = 0.5)
#     ) +
#   geom_text(
#     data = idade_sumario, 
#     aes(x = value, y = 0.5, label = value),
#     hjust = -0.3,
#     vjust = -0.3
#     ) + 
#   theme_minimal() 
#   # theme(
#   #   axis.line.x = element_blank(),
#   #   axis.text.x = element_blank()
#   # )
#   
# p4_03

Sexo

Código
sexo_sumario <- var_summarise_df(gdh_base_clean, "sexo")

# Base dados completa
p4_1 <- gdh_base_clean |>
  filter(
    sexo != "Indeterminado"
  ) |> 
  ggplot() +
  geom_boxplot(
    aes(y = idade, fill = sexo)
    ) +
  # geom_boxplot(
  #   aes(x = 1, y = idade)
  #   ) +
  # geom_text(
  #   data = idade_sumario, 
  #   aes(x = 1, y = value, label = value),
  #   hjust = -0.3,
  #   vjust = -0.3
  #   ) + 
  coord_cartesian() +
  theme_classic() +
  theme(
    axis.line.x = element_blank(),
    axis.text.x = element_blank()
  )
  
p4_1

Código
# Ficava mais bonito ribbon 
Código
# valores únicos 
# hosp_id <- distinct(gdh_base, hosp_id)
# 
# tipo_adm <- distinct(gdh_base, adm_tip)
# 
# patient_id <- distinct(gdh_base, seq_number)
# 
# total_patients <- length(patient_id$seq_number)
# 
# patient_id <- patient_id |> 
#   mutate(
#     age_group = length(patient_id$seq_number)
#   )

# Criar as categorias (European Standard Population (ESP) 2013 e INE) 

# gdh_uls <- gdh_uls |> 
#   mutate(
#     idade_cat_eu = cut(
#       idade,
#       breaks = c(0, 1, 5, 10, 15, 20, 25, 30, 35, 40, 45,
#                  50, 55, 60, 65, 70, 75, 80, 85, 90, 95, Inf),
#       right = FALSE), # Criar nova categoria com divisão do grupo etário do Eurostat para análise
#       idade_cat_ine = cut(
#       idade,
#       breaks = c(0, 5, 10, 15, 20, 25, 30, 35, 40, 45,
#                  50, 55, 60, 65, 70, 75, 80, 85, Inf), 
#       right = FALSE) # Criar nova categoria com divisão do grupo etário do INE para análise
#   )


# Total Episódios por ULS e Tipo de episodio 
total_ep_uls <- gdh_uls |> 
  group_by(uls_2024, sexo, idade, idade_cat, adm_tip) %>%
  summarise(total = n()) %>%
  arrange(uls_2024)

# boxplot ?  

p4_2 <- ggplot(
  gdh_uls, 
  aes(x= idade)
) +
  geom_density(alpha=.4) +
  facet_grid(~uls_2024) +
  labs(title = "Nº Episodios por grupo etário",
       subtitle = "ULS Amadora/Sintra e ULS Médio Ave",
       y = "Densidade",
       x = "Grupo etário", 
       caption = "Autores:") +
  theme_minimal()

p4_2

Código
# p4_3 <- ggplot() +
#   geom_area(
#     gdh_uls,
#     aes(y = ),
#     alpha= 0.4,
#     colour = "black",
#     stat = "bin") +
#   # facet_grid(~uls_2024) +
#   labs(title = "Nº Episodios por grupo etário",
#        subtitle = "ULS Amadora/Sintra e ULS Médio Ave",
#        y = "Densidade",
#        x = "Grupo etário", 
#        caption = "Autores:") +
#   theme_minimal()
# 
# p4_3
Código
p4_3 <- gdh_uls |> 
  group_by(idade_cat) |> 
  ggplot() +
  geom_bar(
    aes(x = uls_2024, fill = idade_cat),
    position = "fill"
  ) +
  geom_text(
    aes(
      x = uls_2024, 
      fill = idade_cat, 
      label = after_stat(count)
      ),
    stat = "count",
    position = position_fill(vjust = 0.5)
    ) +
    theme(
    legend.title = element_blank(),
    legend.position = element_blank(),
    axis.title.x = element_blank(),
    axis.title.y = element_blank()
    ) +
  coord_flip() +
  theme_classic() 


p4_3

Código
p4_31 <- gdh_uls |> 
  ggplot() +
  geom_bar(
    aes(x = uls_2024, fill = idade_cat),
    position = "stack"
  ) +
  geom_text(
    aes(x = uls_2024, fill = idade_cat, label = after_stat(count)),
    stat = "count",
    position = position_stack(vjust = 0.5)
  ) +
  theme_classic() 
  

p4_31

Código
## Idade total por  ULS 
# p4_32 <- gdh_uls |> 
#   count(uls_2024, idade_cat) |> 
#   mutate(
#     prop_total = n/sum(n)
#   ) |> 
#   ggplot() +
#   geom_bar(
#     aes(x = uls_2024, y = prop_total, fill = idade_cat),
#     position = "fill"
#   ) +
#   geom_text(
#     aes(x = prop_total, fill = idade_cat,
#     label = percent(prop_total, accuracy = 1.0)
#     ),
#     stat = "count",# mostrar %
#     position = position_fill(vjust = 0.5)  
#   ) +
#   theme_classic() 
#   
# 
# p4_32

# p4_33 <- gdh_uls |> 
#   mutate(
#     idade_cat = cut(idade, c(0, 18, 65, Inf), right = FALSE)
#   ) |> 
#   ggplot() +
#   geom_bar(
#     aes(x = uls_2024, fill = idade_cat),
#     position = "fill"
#   ) +
#   geom_text(
#     stat = "count",
#     aes(label = c(after_stat(prop), "(n)")),
#     position = position_fill(vjust = 0.5)
#   ) +
#   theme_classic() 
# 
# 
# p4_33

5.0.2 sexo

Código
# Distribuição por sexo ULS 
p4_4 <- total_ep_uls |> 
  ggplot(aes(x = uls_2024, fill = sexo)) +
  geom_bar(position = "fill") +
  scale_fill_viridis_d(end = .5) +
 # facet_grid(~uls_2024) +
  theme(
   # axis.text.y = element_blank(),
    # axis.ticks.y = element_blank(),
    axis.line.x  = element_blank(),
    axis.line.y = element_blank(),
    axis.title.x = element_blank(),
    axis.title.y = element_blank(),
    )

p4_4

Código
# 
total_uls_sex_age <- gdh_uls |> 
  group_by(uls_2024, adm_tip, sexo = as.factor(sexo), idade_cat) %>%
  summarise(total = n()) %>%
  arrange(sexo)


tabela4 <- tbl_summary(
  gdh_uls,
  include = c(uls_2024, adm_tip, sexo, idade_cat),
  by = sexo, # Dividir a tabela pelo estado da doença cardíaca
  missing = "ifany", # Excluir dados ausentes do resumo
) |>
  modify_spanning_header(c("stat_1", "stat_2") ~ "**Sexo**") %>%
  add_n() |>  # Adicionar uma coluna para contar observações não ausentes
  # add_p() |>  # Realizar testes estatísticos para comparar grupos
  modify_header(label = "**Variável**") |>  # Personalizar o cabeçalho da tabela
  bold_labels() # Tornar as etiquetas em negrito para ênfase

tabela4 <- tabela4 |> 
  as_gt() |>  
  tab_header(
    title = md("**Episódios por ULS**") ,
    subtitle = "Por sexo"
  ) |>
  tab_source_note(
    source_note = md("Fonte: gdh")
  ) |>
  fmt_number( decimals = 3) |>   
  opt_stylize(style = 1, color = "gray") |> 
  opt_align_table_header(align = "left") 

tabela4
Episódios por ULS
Por sexo
Variável N
Sexo
Feminino
N = 64,2311
Masculino
N = 58,6171
uls_2024 122,848

    ULS de Amadora/Sintra
43,417 (68%) 38,384 (65%)
    ULS do Médio Ave
20,814 (32%) 20,233 (35%)
adm_tip 122,848

    Programada
40,687 (63%) 39,674 (68%)
    Urgente
23,544 (37%) 18,943 (32%)
idade_cat 122,848

    [0,18)
6,305 (9.8%) 7,615 (13%)
    [18,65)
34,209 (53%) 24,021 (41%)
    [65,Inf)
23,717 (37%) 26,981 (46%)
1 n (%)
Fonte: gdh
Código
## Deixar por sexo, retirar esta 
# tabela2 <- tbl_summary(
#   gdh_uls,
#   include = c(uls_2024, adm_tip, sexo, idade_cat_ine, dsp, descricao_apr31),
#   by = uls_2024, # Dividir a tabela pelo estado da doença cardíaca
#   missing = "ifany", # Excluir dados ausentes do resumo
# ) |>
#   modify_spanning_header(c("stat_1", "stat_2") ~ "**ULS**") %>%
#   add_n() |>  # Adicionar uma coluna para contar observações não ausentes
#   # add_p() |>  # Realizar testes estatísticos para comparar grupos
#   modify_header(label = "**Variável**") |>  # Personalizar o cabeçalho da tabela
#   bold_labels() # Tornar as etiquetas em negrito para ênfase
# 
# tabela2 <- tabela2 |> 
#   as_gt() |>  
#   tab_header(
#     title = md("**Risco por ULS**") ,
#     subtitle = "Por sexo e idade"
#   ) |>
#   tab_source_note(
#     source_note = md("Fonte: gdh")
#   ) |>
#   fmt_number( decimals = 3) |>   
#   opt_stylize(style = 1, color = "gray") |> 
#   opt_align_table_header(align = "left") 
# 
# tabela2


# Contar total de observações por grupo etário
contagem_idade <- gdh_uls %>%
  group_by(uls_2024, as.factor(sexo), idade_cat) %>%
  summarise(total = n()) %>%
  arrange(idade_cat)

# p5 <- total_ep_uls |> 
#   ggplot() +  
#   geom_bar(aes(x = adm_tip, fill = sexo), 
#            stat = "count") + 
#   geom_text(
#     aes(x = adm_tip),
#     label = "total",
#     stat = "identity",
#     position = position_stack(vjust = 0.5)
#   ) +
#   facet_wrap(~uls_2024) +  # Dividir o gráfico em facetas com base na variável 'tipo_de_especialidade'
#   theme_classic()
# 
# p5

5.0.3 Proporções

Código
# grafico barras por uls e severidade problema - considerar fazer dois separados "dodge" 
# como fazer aparecer por percentagens? 

p6 <- gdh_uls |> 
  count(uls_2024, severidade_apr31) |> 
  mutate(freq = (n / sum(n))) |> 
  mutate(cum = cumsum(freq)) |> 
  ggplot(
    aes(x = uls_2024, y = freq, fill = severidade_apr31)
    )+
  geom_bar(position = "stack", stat = "identity") +
  scale_fill_viridis_d(end = .5) +
  geom_text(aes(label = percent(freq, accuracy = .1)), position = position_stack(vjust = .1), colour = "white") +
  labs(fill = "severidade_apr31", y = "Proportion", x = "ULS 2024")

# low hanging fruit: residentes municipio (INE) 2017 - 
# NAs - tirar valores; retirar variaveis?; 
# merge()
# fazer tabela com os missings 

p6

Código
## !!! transformar em função  

5.1 Perfil de morbilidade

Código
# total_mort_apr31 <- gdh_uls |>
#   mutate(
#     mort_cat = factor(mortalidade_apr31)
#   ) |> 
#     group_by(uls_2024, mort_cat) |> 
#   summarise(total = n(), .groups = "drop")  |> 
#   arrange(uls_2024)

total_mort_apr31 <- gdh_uls |>
  mutate(
    mortalidade_apr31 = na_if(mortalidade_apr31, ""),         # transforma "" em NA
    mort_cat = fct_explicit_na(factor(mortalidade_apr31), na_level = "NA")
  ) |>
  group_by(uls_2024, mort_cat) |>
  summarise(total = n(), .groups = "drop")

p7 <- total_mort_apr31 |>  
  ggplot(
    aes(x = uls_2024, y = total, fill = mort_cat)
    )+
  geom_bar(position = "stack", stat = "identity") +
  scale_fill_viridis_d(end = 0.5) +
  geom_text(aes(label = total), position = position_stack(vjust = 1), colour = "white") +
  labs(y = "Total de episódios", x = "ULS", fill = "Severidade")

p7

Código
#   group_by(uls_2024, mort_cat) %>%
#   summarise(total = n()) #%>%
# arrange(uls_2024)

# total_mort_apr31 <- gdh_uls |>
#   mutate(
#     mort_cat = ifelse(is.na(mortalidade_apr31), "NA", as.character(mortalidade_apr31)),
#     mort_cat = factor(mort_cat)
#   ) |>
  #

# Freq Mortalidade
p7_1 <- gdh_uls |> 
  count(uls_2024, mortalidade_apr31) |> 
  mutate(freq = (n / sum(n))) |> 
  mutate(cum = cumsum(freq)) |> 
  ggplot(
    aes(x = uls_2024, y = freq, fill = mortalidade_apr31)
    )+
  geom_bar(position = "fill", stat = "identity") +
  scale_fill_viridis_d(end = 0.5) +
  geom_text(aes(label = percent(freq, 1), (vjust = 1), colour = "white")) +
  labs(fill = "mortalidade_apr31", y = "Proportion", x = "ULS 2024")

p7_1

6

Código
gdh_ridges <- gdh_base_join |> 
  left_join(
    geo_linkage_clean,
    by = "dt_mun"
  )

anti_join(gdh_base_join, geo_linkage_clean, by = "dt_mun")
 [1] seq_number        sexo              idade             cod_dist         
 [5] cod_conc          dias_int          dsp               adm_tip          
 [9] gcd_apr31         tipo_port_apr31   severidade_apr31  mortalidade_apr31
[13] icd10_code        cod_dist_chr      cod_conc_chr      dt_mun           
[17] code_length       idade_cat         descricao_en      descricao_pt     
[21] descricao_apr31  
<0 rows> (or 0-length row.names)
Código
# nem todos tinham distrito/municipio, havia NA 
# gdh_ridges_unique <- gdh_ridges |> 
#   group_by(distrito_2013) |> 
#   mutate(
#     municipio_2013_f = as.factor(municipio_2013)
#     )
#   )
#   # filter(
#   #   distrito_2013 == NA
#   # ) 
# 
# ridges_plot <- gdh_ridges |> 
#   filter(
#     cod_dist_chr.x != "99",
#     cod_conc_chr.x != "99" 
#   )

p8 <- gdh_ridges |>
  group_by(regiao_2024) |> 
  ggplot(aes(x = idade, y = regiao_2024)) + 
  geom_density_ridges() +
  theme_classic()

p8

Código
p8_1 <- gdh_uls |>
  group_by(uls_2024) |> 
  ggplot(aes(x = idade, y = uls_2024)) + 
  geom_density_ridges() +
  theme_classic()

p8_1

Código
# fazer por distrito 

p8_2 <- gdh_uls |>
  group_by(dsp) |> 
  ggplot(aes(x = idade, y = dsp)) + 
  geom_density_ridges() +
  theme_classic()

p8_2

Código
p8_3 <- gdh_uls |>
  group_by(sexo) |> 
  ggplot(aes(x = idade, y = sexo)) + 
  geom_density_ridges() +
  theme_classic()

p8_3

Código
dias_int_sum <- var_summarise_df(gdh_base_clean, "dias_int")

dias_int_sum <-  dias_int_sum |> 
  mutate(
    value = round(Value, 2)
  )


# p8_4 <- gdh_uls |> 
#   filter(
#    1 <= dias_int <10
#   ) |> 
#   group_by(idade_cat) |> 
#   ggplot(aes(x = dias_int, y = idade_cat)) + 
#   geom_density_ridges() +
#   theme_classic()
#   
# p8_4

7 5. Visualização dos dados

8 6. Bibliografia/Referências

Escola Nacional de Saúde Pública (ENSP NOVA). 2025. «Fundamentos de Data Science na Saúde: Aplicações com R». Curso de formação avançada.
Nota

A versão deste documento foi atualizada no dia 16/11/2025.